package mylog

import (
	"errors"
	"fmt"
	"net/url"
	"strings"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	lumberjack "gopkg.in/natefinch/lumberjack.v2"
)

type lumberjackSink struct {
	*lumberjack.Logger
}

func (lumberjackSink) Sync() error {
	return nil
}

var myLogger *zap.Logger
var errLogger *zap.Logger

// var SugarLogger *zap.SugaredLogger
var mySugar *zap.SugaredLogger
var errSugar *zap.SugaredLogger

var myConfig zap.Config
var errConfig zap.Config

func myTrimmedPath(ec zapcore.EntryCaller) string {
	if !ec.Defined {
		return "undefined"
	}

	path := ec.FullPath()

	idx := strings.LastIndexByte(ec.File, '/')
	if idx != -1 {
		idx = strings.LastIndexByte(ec.File[:idx], '/')
		if idx != -1 {
			path = ec.File[idx+1:]
		}
	}

	str := fmt.Sprintf("%s:%d", path, int64(ec.Line))
	return fmt.Sprintf("%-25s", str)
}

func myShortCallerEncoder(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(myTrimmedPath(caller))
}

func myCapitalLevelEncoder(l zapcore.Level, enc zapcore.PrimitiveArrayEncoder) {
	enc.AppendString(fmt.Sprintf("%-5s", l.CapitalString()))
}

func init() {
	MylogInit("", true, "dbg")
}

func MylogInit(filename string, to_stdout bool, log_level string) error {
	if filename == "" && !to_stdout {
		return errors.New("arg err")
	}

	var err error
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:          "time",
		LevelKey:         "level",
		NameKey:          "logger",
		CallerKey:        "caller",
		MessageKey:       "msg",
		StacktraceKey:    "stacktrace",
		LineEnding:       "\n",
		EncodeLevel:      myCapitalLevelEncoder,                            // zapcore.CapitalLevelEncoder, zapcore.CapitalColorLevelEncoder,
		EncodeTime:       zapcore.TimeEncoderOfLayout("0102 15:04:05.000"), //zapcore.ISO8601TimeEncoder,
		EncodeDuration:   zapcore.SecondsDurationEncoder,
		EncodeCaller:     myShortCallerEncoder,
		ConsoleSeparator: "|",
	}

	logFile := filename

	if logFile != "" {
		ll := lumberjack.Logger{
			Filename:   logFile,
			MaxSize:    10, //MB
			MaxBackups: 5,
			LocalTime:  true,
			// MaxAge:     90, //days
			Compress: false,
		}
		zap.RegisterSink("lumberjack", func(*url.URL) (zap.Sink, error) {
			return lumberjackSink{
				Logger: &ll,
			}, nil
		})
	}

	myConfig = zap.Config{
		Level:             zap.NewAtomicLevelAt(zapcore.DebugLevel),
		Development:       false,
		Encoding:          "console", //"json",
		EncoderConfig:     encoderConfig,
		DisableStacktrace: true,
		// InitialFields:    map[string]interface{}{"MyName": "kainhuck"},
		OutputPaths:      []string{ /* "stdout", */ },
		ErrorOutputPaths: []string{"stdout"},
	}

	if logFile != "" {
		myConfig.OutputPaths = append(myConfig.OutputPaths, fmt.Sprintf("lumberjack:%s", logFile))
	}

	if to_stdout {
		myConfig.OutputPaths = append(myConfig.OutputPaths, "stdout")
	}

	switch log_level {
	case "info":
		myConfig.Level = zap.NewAtomicLevelAt(zapcore.InfoLevel)
	case "error", "err":
		myConfig.Level = zap.NewAtomicLevelAt(zapcore.ErrorLevel)
	}

	myLogger, err = myConfig.Build(zap.AddCallerSkip(1))
	if err != nil {
		return err
	}

	mySugar = myLogger.Sugar()

	if filename != "" {
		return MylogInitErr(filename)
	}

	return nil
}

// ERR单独存一个文件
func MylogInitErr(filename string) error {
	var err error
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:          "time",
		LevelKey:         "level",
		NameKey:          "logger",
		CallerKey:        "caller",
		MessageKey:       "msg",
		StacktraceKey:    "stacktrace",
		LineEnding:       "\n",
		EncodeLevel:      myCapitalLevelEncoder,                            // zapcore.CapitalLevelEncoder, zapcore.CapitalColorLevelEncoder,
		EncodeTime:       zapcore.TimeEncoderOfLayout("0102 15:04:05.000"), //zapcore.ISO8601TimeEncoder,
		EncodeDuration:   zapcore.SecondsDurationEncoder,
		EncodeCaller:     myShortCallerEncoder,
		ConsoleSeparator: "|",
	}

	logFile := ""
	dotidx := strings.LastIndex(filename, ".")
	if dotidx >= 0 {
		logFile = filename[0:dotidx] + "_err" + filename[dotidx:]
	} else {
		logFile = filename + "_err"
	}

	ll := lumberjack.Logger{
		Filename:   logFile,
		MaxSize:    10, //MB
		MaxBackups: 5,
		LocalTime:  true,
		// MaxAge:     90, //days
		Compress: false,
	}
	zap.RegisterSink("lumberjackerr", func(*url.URL) (zap.Sink, error) {
		return lumberjackSink{
			Logger: &ll,
		}, nil
	})

	errConfig = zap.Config{
		Level:             zap.NewAtomicLevelAt(zapcore.ErrorLevel),
		Development:       false,
		Encoding:          "console", //"json",
		EncoderConfig:     encoderConfig,
		DisableStacktrace: true,
		// InitialFields:    map[string]interface{}{"MyName": "kainhuck"},
		OutputPaths:      []string{ /* "stdout", */ fmt.Sprintf("lumberjackerr:%s", logFile)},
		ErrorOutputPaths: []string{"stdout"},
	}

	errLogger, err = errConfig.Build(zap.AddCallerSkip(1))
	if err != nil {
		return err
	}

	errSugar = errLogger.Sugar()
	return nil
}

func ChangeLevel(newlevel string) {
	switch newlevel {
	case "info":
		myConfig.Level.SetLevel(zapcore.InfoLevel)
	case "error", "err":
		myConfig.Level.SetLevel(zapcore.ErrorLevel)
	case "debug", "dbg":
		myConfig.Level.SetLevel(zapcore.DebugLevel)
	}
}

// Debug uses fmt.Sprint to construct and log a message.
func Debug(args ...interface{}) {
	mySugar.Debug(args...)
}

func DebugIf(cond bool, args ...interface{}) {
	if cond {
		mySugar.Debug(args...)
	}
}

// Info uses fmt.Sprint to construct and log a message.
func Info(args ...interface{}) {
	mySugar.Info(args...)
}

func InfoIf(cond bool, args ...interface{}) {
	if cond {
		mySugar.Info(args...)
	}
}

// Warn uses fmt.Sprint to construct and log a message.
func Warn(args ...interface{}) {
	mySugar.Warn(args...)
}

func WarnIf(cond bool, args ...interface{}) {
	if cond {
		mySugar.Warn(args...)
	}
}

// Error uses fmt.Sprint to construct and log a message.
func Error(args ...interface{}) {
	mySugar.Error(args...)
	if errSugar != nil {
		errSugar.Error(args...)
	}
}

func ErrorIf(cond bool, args ...interface{}) {
	if cond {
		mySugar.Error(args...)
		if errSugar != nil {
			errSugar.Error(args...)
		}
	}
}

// Panic uses fmt.Sprint to construct and log a message, then panics.
func Panic(args ...interface{}) {
	mySugar.Panic(args...)
}

func PanicIf(cond bool, args ...interface{}) {
	if cond {
		mySugar.Panic(args...)
	}
}

// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.
func Fatal(args ...interface{}) {
	mySugar.Fatal(args...)
}

func FatalIf(cond bool, args ...interface{}) {
	if cond {
		mySugar.Fatal(args...)
	}
}

func Debugf(template string, args ...interface{}) {
	mySugar.Debugf(template, args...)
}

func DebugfIf(cond bool, template string, args ...interface{}) {
	if cond {
		mySugar.Debugf(template, args...)
	}
}

func Infof(template string, args ...interface{}) {
	mySugar.Infof(template, args...)
}

func InfofIf(cond bool, template string, args ...interface{}) {
	if cond {
		mySugar.Infof(template, args...)
	}
}

func Warnf(template string, args ...interface{}) {
	mySugar.Warnf(template, args...)
}

func WarnfIf(cond bool, template string, args ...interface{}) {
	if cond {
		mySugar.Warnf(template, args...)
	}
}

func Errorf(template string, args ...interface{}) {
	mySugar.Errorf(template, args...)
	if errSugar != nil {
		errSugar.Errorf(template, args...)
	}
}

func ErrorfIf(cond bool, template string, args ...interface{}) {
	if cond {
		mySugar.Errorf(template, args...)
		if errSugar != nil {
			errSugar.Errorf(template, args...)
		}
	}
}

func Panicf(template string, args ...interface{}) {
	mySugar.Panicf(template, args...)
}

func PanicfIf(cond bool, template string, args ...interface{}) {
	if cond {
		mySugar.Panicf(template, args...)
	}
}

func Fatalf(template string, args ...interface{}) {
	mySugar.Fatalf(template, args...)
}

func FatalfIf(cond bool, template string, args ...interface{}) {
	if cond {
		mySugar.Fatalf(template, args...)
	}
}
